package com.easyedu.dean.service;

import com.easyedu.core.dao.BuildingAddressDao;
import com.easyedu.core.dao.ClassRoomDao;
import com.easyedu.core.dao.InstituteDao;
import com.easyedu.core.dao.LearnBuildingDao;
import com.easyedu.core.dao.OpenCourseDao;
import com.easyedu.core.dao.OpenCourseTimeDao;
import com.easyedu.core.dao.OpenCourseTimeMidDao;
import com.easyedu.core.dao.OpenCourseTimeVersionDao;
import com.easyedu.core.dao.ResidenceHallsDao;
import com.easyedu.core.dao.StudentDao;
import com.easyedu.core.entity.BaseError;
import com.easyedu.core.entity.DbError;
import com.easyedu.core.entity.DeanServiceError;
import com.easyedu.core.entity.dto.DepartmentYear;
import com.easyedu.core.entity.po.BuildingAddress;
import com.easyedu.core.entity.po.Classroom;
import com.easyedu.core.entity.po.HasArrangeCourse;
import com.easyedu.core.entity.po.OpenCourse;
import com.easyedu.core.entity.po.OpenCourseTime;
import com.easyedu.core.entity.po.OpenCourseTimeMid;
import com.easyedu.core.entity.po.OpenCourseTimeVersion;
import com.easyedu.core.entity.po.ResidenceHalls;
import com.easyedu.core.exception.EasyEduException;
import com.easyedu.core.manager.TimeTableManager;
import com.easyedu.core.manager.TimeTableVersionManager;
import com.easyedu.dean.entity.dto.request.ReqCancelArrange;
import com.easyedu.dean.entity.dto.request.ReqManualArrange;
import com.easyedu.dean.entity.dto.request.ReqQueryVersionHasArrangeCourse;
import com.easyedu.dean.entity.dto.response.QueryArrangeCourseStatus;
import com.easyedu.dean.entity.dto.response.RespOpenCourseRange;
import com.easyedu.dean.entity.dto.response.RespQueryHasArrangeCourse;
import com.easyedu.dean.service.arrange.DepartmentPackageService;
import com.easyedu.dean.service.arrange.DepartmentPackageService.CoursePackage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.apache.ibatis.annotations.Delete;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author linxi
 * @date 2022/3/20
 * @describe 排课服务
 **/
@Service
public class ArrangeService {

     private final OpenCourseDao openCourseDao;

     private final ClassRoomDao classRoomDao;

     private final OpenCourseTimeMidDao openCourseTimeMidDao;

     private final StudentDao studentDao;

     private final BuildingAddressDao buildingAddressDao;

     private final ResidenceHallsDao residenceHallsDao;

     private final OpenCourseTimeVersionDao openCourseTimeVersionDao;

     private final TimeTableVersionManager timeTableVersionManager;

     private final OpenCourseTimeDao openCourseTimeDao;

     private final TimeTableManager timeTableManager;

     public ArrangeService(OpenCourseDao openCourseDao, ClassRoomDao classRoomDao,
             OpenCourseTimeMidDao openCourseTimeMidDao, InstituteDao instituteDao, StudentDao studentDao,
             BuildingAddressDao buildingAddressDao, ResidenceHallsDao residenceHallsDao,
             OpenCourseTimeVersionDao openCourseTimeVersionDao, TimeTableVersionManager timeTableVersionManager,
             OpenCourseTimeDao openCourseTimeDao, TimeTableManager timeTableManager) {

          this.openCourseDao = openCourseDao;
          this.classRoomDao = classRoomDao;
          this.openCourseTimeMidDao = openCourseTimeMidDao;
          this.studentDao = studentDao;
          this.buildingAddressDao = buildingAddressDao;
          this.residenceHallsDao = residenceHallsDao;
          this.openCourseTimeVersionDao = openCourseTimeVersionDao;
          this.timeTableVersionManager = timeTableVersionManager;
          this.openCourseTimeDao = openCourseTimeDao;
          this.timeTableManager = timeTableManager;
     }

     @Transactional(rollbackFor = Exception.class)
     public List<OpenCourse> autoArrange3(String versionUUID) throws RuntimeException {

          //校验排课版本是否存在
          OpenCourseTimeVersion openCourseTimeVersion = timeTableVersionManager.getTimeTableVersion(versionUUID);
          int openYear = openCourseTimeVersion.getOpenYear();
          String semester = openCourseTimeVersion.getSemester();
          List<OpenCourse> resNotArrangeCourse = new ArrayList<>();
          //教室排课map,<教室id，>
          //123456
          //周一 上午 下午｜ 周二 上午 下午｜周三 上午 下午
          Map<Long, boolean[]> classroomIsEmptyMap = getClassRoomStatus(openYear, semester, versionUUID);

          //获取排课专业顺序
          List<DepartmentYear> departmentYearList = getArrangeInstitute(openYear, semester);
          //遍历每个专业，寻找专业所在最近的教学楼
          for (DepartmentYear departmentYear : departmentYearList) {
               //获取当前年级是几年级
               String schoolYear = getSchoolYear(openYear, semester, departmentYear.getGrade());
               //获取专业所有待排课程
               List<OpenCourse> notArrangeCourse = openCourseDao
                       .getNotArrangeCourse(departmentYear.getDepartmentId(), openYear, schoolYear, semester,
                               versionUUID);
               if (notArrangeCourse == null || notArrangeCourse.isEmpty()) {
                    continue;
               }
               DepartmentPackageService departmentPackageService = new DepartmentPackageService();
               departmentPackageService.init(departmentYear, notArrangeCourse, openCourseTimeMidDao, versionUUID);

               //获取专业宿舍最近的教学楼列表
               List<BuildingAddress> buildingAddressList = getNearBuildingList(departmentYear);
               //遍历教学楼的每层楼的每个教室
               for (BuildingAddress buildingAddress : buildingAddressList) {
                    int floor = getBuildingFloor(buildingAddress);
                    for (int i = 1; i <= floor; i++) {
                         //获取楼层按照从小到大教室进行排序
                         List<Classroom> classroomList = getASCSortBuildingClassRoomByFloor(buildingAddress, i);
                         for (Classroom classroom : classroomList) {
                              //教室被排课状态
                              boolean[] classroomStatus =
                                      classroomIsEmptyMap.getOrDefault(classroom.getClassroomId(), new boolean[6]);
                              for (int packageNumber = 0; packageNumber < classroomStatus.length; packageNumber++) {
                                   //未排课
                                   if (!classroomStatus[packageNumber]) {
                                        CoursePackage nextPackage =
                                                departmentPackageService.getNextPackage(classroom, packageNumber);
                                        if (nextPackage != null) {
                                             //从课程的1-12遍历，每次拍一个课程需要将其他课程重新选择可排位置。
                                             //如果一门课程，不同的老师，如果有两个则将其固定为 12 21/34 43，且教室相邻【优先级最高，因为相当于时排两门课】。减少学生移动路径
                                             //将四节课绑定为一个时间块，最好是两节课程名字相同的课。
                                             //两个教室一起排课
                                             classroomStatus[packageNumber] = true;
                                             sortCourse(nextPackage, classroom, packageNumber,
                                                     openCourseTimeVersion.getOpenCourseTimeMidVersionId(),
                                                     openCourseTimeVersion.getOpenCourseTimeMidVersionName());
                                        }
                                   }
                              }
                              classroomIsEmptyMap.put(classroom.getClassroomId(), classroomStatus);

                         }

                    }
               }
               resNotArrangeCourse.addAll(departmentPackageService.getCannotArrangeCourse());
          }
          return resNotArrangeCourse;
     }

     private Map<Long, boolean[]> getClassRoomStatus(int openYear, String semester, String versionId) {

          Map<Long, boolean[]> res = new HashMap<>();
          List<OpenCourseTime> classRoomStatus = openCourseTimeMidDao.getClassRoomStatus(openYear, semester, versionId);
          List<Classroom> classroomList = classRoomDao.getClassroom();
          for (Classroom classroom : classroomList) {
               boolean[] booleans = new boolean[6];
               //赋予教室状态
               for (OpenCourseTime roomStatus : classRoomStatus) {
                    if (roomStatus.getClassroom().equals(classroom.getClassroomName())) {
                         int temp = 0;
                         int openDay = roomStatus.getOpenDay();
                         int openLesson = roomStatus.getOpenLesson();
                         if (openDay == 1 || openDay == 4) {
                              if (openLesson == 1 || openLesson == 2) {
                                   temp = 0;
                              } else if (openLesson == 3 || openLesson == 4) {
                                   temp = 1;
                              }
                         } else if (openDay == 2 || openDay == 5) {
                              if (openLesson == 1 || openLesson == 2) {
                                   temp = 2;
                              } else if (openLesson == 3 || openLesson == 4) {
                                   temp = 3;
                              }
                         } else if (openDay == 3) {
                              if (openLesson == 1 || openLesson == 2) {
                                   temp = 4;
                              } else if (openLesson == 3 || openLesson == 4) {
                                   temp = 5;
                              }
                         }
                         booleans[temp] = true;
                    }
               }
               res.put(classroom.getClassroomId(), booleans);
          }

          return res;
     }

     /**
      * 获取当前年份、学期 下各个专业对应的年级的 人数 倒序列表
      *
      * @param openYear 开课年
      * @param semester 开课学期
      * @return 专业列表
      */
     private List<DepartmentYear> getArrangeInstitute(int openYear, String semester) {

          openYear -= 3;
          int startGrade;
          int endGrade;
          if (semester.equals("第一学期")) {
               startGrade = openYear;
          } else if (semester.equals("第二学期")) {
               startGrade = openYear - 1;
          } else {
               throw new EasyEduException(BaseError.ILLEGAL_DATA, "学期非法：err:" + semester);
          }
          //[startGrade, endGrade],所以是+3
          endGrade = openYear + 3;
          return studentDao.getInstituteStudentNumberList(startGrade, endGrade);
     }

     private String getSchoolYear(int openYear, String semester, int grade) {

          int i = openYear - grade;
          if (semester.equals("第一学期")) {
               i += 1;
          } else if (semester.equals("第二学期")) {

          } else {
               throw new EasyEduException(BaseError.ILLEGAL_DATA, "学期非法：err:" + semester);
          }
          switch (i) {
               case 1:
                    return "一年级";
               case 2:
                    return "二年级";
               case 3:
                    return "三年级";
               case 4:
                    return "四年级";
               default:
                    throw new EasyEduException(BaseError.ILLEGAL_DATA, "当前年级与目标专业年级差距大于4");

          }
     }

     private List<BuildingAddress> getNearBuildingList(DepartmentYear institute) {
          //获取所有教学楼
          List<BuildingAddress> builds = buildingAddressDao.getBuild();
          //获取专业宿舍楼
          List<ResidenceHalls> departmentHalls = residenceHallsDao.getDepartmentHalls(institute.getDepartmentId());
          Map<Double, List<BuildingAddress>> hash = new HashMap<>();
          for (ResidenceHalls halls : departmentHalls) {
               //获取教学楼和宿舍楼的距离
               for (BuildingAddress build : builds) {

                    double length = Math.sqrt(
                            Math.pow((halls.getX() - build.getX()), 2) + Math.pow((halls.getY() - build.getY()), 2));
                    List<BuildingAddress> orDefault = hash.getOrDefault(length, new ArrayList<>());
                    orDefault.add(build);
                    hash.put(length, orDefault);
               }
          }
          List<BuildingAddress> res = new ArrayList<>();
          double min = Double.MAX_VALUE;
          while (!hash.isEmpty()) {
               for (Double aDouble : hash.keySet()) {
                    if (aDouble < min) {
                         min = aDouble;
                    }
               }
               res.addAll(hash.get(min));
               hash.remove(min);
               min = Double.MAX_VALUE;
          }
          return res;
     }

     private int getBuildingFloor(BuildingAddress buildingAddress) {

          return buildingAddressDao.getBuildingMaxFloor(buildingAddress.getBuildName());
     }

     private List<Classroom> getASCSortBuildingClassRoomByFloor(BuildingAddress buildingAddress, int floor) {

          return classRoomDao.getClassroomBySeat(buildingAddress.getBuildName(), floor);
     }

     private void sortCourse(CoursePackage coursePackage, Classroom classroom, int packageNumber,
             String openCourseTimeMidVersionId, String openCourseTimeMidVersionName) {

          long[] openCourseIdArray = coursePackage.getOpenCourseIdArray();

          for (int i = 0; i < openCourseIdArray.length; i++) {
               long l = openCourseIdArray[i];
               if (l == 0) {
                    continue;
               }
               int openDay = (packageNumber / 2 + 1);
               long classLesson;
               if (packageNumber == 0 || packageNumber == 2 || packageNumber == 4) {
                    classLesson = i + 1;
               } else {
                    if (i == 0) {
                         classLesson = 3;
                    } else {
                         classLesson = 4;
                    }
               }
               putCourseToMidTimeDB(classroom.getClassroomName(), openDay, 1, classLesson, openCourseTimeMidVersionId,
                       openCourseTimeMidVersionName, l);

          }
     }

     private void putCourseToMidTimeDB(String classroom, long openDay, long openWeek, long openLesson, String versionId,
             String versionName, Long openCoursesId) {

          OpenCourse openCourseById = openCourseDao.getOpenCourseById(openCoursesId);
          if (openCourseById == null || openCourseById.getOpenCoursesId() == 0) {
               throw new EasyEduException(DeanServiceError.ERR_OPEN_COURSE_NOT_EXIST);
          }
          //检查一周需要排几次课
          int nextDay = 0;
          if (openDay == 1 || openDay == 2) {
               nextDay = 3;
          }
          //计算一共需要排几周课
          long theoryClassHour = openCourseById.getTheoryClassHour();
          long weekNumber = theoryClassHour / 2;
          if (nextDay != 0) {
               weekNumber /= 2;
          }
          //检查教室是否已经被排课
          for (long i = openWeek; i <= weekNumber + openWeek; i++) {
               if (classroomHasArrange(i, openDay, openLesson, classroom, versionId)) {
                    throw new EasyEduException(DeanServiceError.ERR_CLASS_ROOM_NOT_EMPTY,
                            "此教室已被占用;时间:第" + i + "周,周" + openDay + ",第" + openLesson + "大节");
               }
               if (nextDay != 0) {
                    if (classroomHasArrange(i, openDay + nextDay, openLesson, classroom, versionId)) {
                         throw new EasyEduException(DeanServiceError.ERR_CLASS_ROOM_NOT_EMPTY,
                                 "此教室已被占用;时间:第" + i + "周,周" + openDay + nextDay + ",第" + openLesson + "大节");
                    }
               }
          }
          for (long i = openWeek; i <= weekNumber + openWeek; i++) {
               //进行排课
               Integer integer = openCourseTimeMidDao
                       .createFromOpenCourseAndPara(openCourseById.getOpenCoursesId(), i, openDay, openLesson,
                               versionId, versionName, classroom);
               if (integer == 0) {
                    throw new EasyEduException(DbError.Err_INSERT_DB_ERROR, "排课时发生意外,无法对课程进行开课");
               }
               if (nextDay != 0) {
                    integer = openCourseTimeMidDao
                            .createFromOpenCourseAndPara(openCourseById.getOpenCoursesId(), i, openDay + nextDay,
                                    openLesson, versionId, versionName, classroom);
                    if (integer == 0) {
                         throw new EasyEduException(DbError.Err_INSERT_DB_ERROR, "排课时发生意外,无法对课程进行开课");
                    }
               }

          }
     }

     private boolean classroomHasArrange(long openWeek, long openDay, long openLesson, String classroom,
             String versionId) {

          OpenCourseTime byClassroom =
                  openCourseTimeMidDao.getByClassroom(openWeek, openDay, openLesson, classroom, versionId);
          return byClassroom != null && byClassroom.getOpenCoursesId() != 0;
     }

     @Transactional(rollbackFor = Exception.class)
     public void cancelArrange(ReqCancelArrange reqCancelArrange) {
          String versionId = reqCancelArrange.getVersionId();
          //校验排课版本是否存在
          OpenCourseTimeVersion openCourseTimeVersion = openCourseTimeVersionDao.getByVersionId(versionId);
          if (openCourseTimeVersion == null || openCourseTimeVersion.getId() == 0) {
               throw new EasyEduException(DeanServiceError.ERR_OPEN_COURSE_TIME_VERSION_NOT_EXIST);
          }
          List<String> openCourseNumbers = reqCancelArrange.getOpenCourseNumbers();
          if (openCourseNumbers == null || openCourseNumbers.isEmpty()) {
               throw new EasyEduException(BaseError.ILLEGAL_DATA, "未传入任何课程序号");
          }
          for (String openCourseNumber : openCourseNumbers) {
               Integer integer = openCourseTimeMidDao
                       .cancelOpenCourse(openCourseNumber, openCourseTimeVersion.getOpenCourseTimeMidVersionId());
               if (integer == 0) {
                    throw new EasyEduException(DbError.Err_UPDATE_DB_ERROR, "取消课程序号为:" + openCourseNumber + "的课程失败");
               }
          }

     }

     public QueryArrangeCourseStatus getArrangeCourseStatus(String versionId) {
          //校验排课版本是否存在
          OpenCourseTimeVersion openCourseTimeVersion = openCourseTimeVersionDao.getByVersionId(versionId);
          if (openCourseTimeVersion == null || openCourseTimeVersion.getId() == 0) {
               throw new EasyEduException(DeanServiceError.ERR_OPEN_COURSE_TIME_VERSION_NOT_EXIST);
          }
          //获取已排课课程
          List<OpenCourse> hasArrangeCourse = openCourseDao
                  .getHasArrangeCourseByStatus(openCourseTimeVersion.getOpenYear(), openCourseTimeVersion.getSemester(),
                          versionId);
          //获取未排课课程
          List<OpenCourse> notArrangeCourse = openCourseDao
                  .getNotArrangeCourseByStatus(openCourseTimeVersion.getOpenYear(), openCourseTimeVersion.getSemester(),
                          versionId);
          return new QueryArrangeCourseStatus(hasArrangeCourse, notArrangeCourse);
     }

     @Transactional(rollbackFor = Exception.class)
     public void manualArrange(ReqManualArrange reqManualArrange) {

          String classroom = reqManualArrange.getClassroom();
          long openCoursesId = reqManualArrange.getOpenCoursesId();
          long openDay = reqManualArrange.getOpenDay();
          long openWeek = reqManualArrange.getOpenWeek();
          long openLesson = reqManualArrange.getOpenLesson();
          String versionId = reqManualArrange.getVersionId();
          //查询版本
          OpenCourseTimeVersion openCourseTimeVersion = openCourseTimeVersionDao.getByVersionId(versionId);
          if (openCourseTimeVersion == null || openCourseTimeVersion.getId() == 0) {
               throw new EasyEduException(DeanServiceError.ERR_OPEN_COURSE_TIME_VERSION_NOT_EXIST);
          }
          //检查课程是否已经被排课
          List<OpenCourseTimeMid> arrangeCourseStatus =
                  openCourseTimeMidDao.getArrangeCourseStatus(openCoursesId, versionId);
          if (arrangeCourseStatus != null && arrangeCourseStatus.size() != 0) {
               throw new EasyEduException(DeanServiceError.ERR_OPEN_COURSE_ARRANGED);
          }

          putCourseToMidTimeDB(classroom, openDay, openWeek, openLesson,
                  openCourseTimeVersion.getOpenCourseTimeMidVersionId(),
                  openCourseTimeVersion.getOpenCourseTimeMidVersionName(), openCoursesId);
     }

     public void addArrangeVersion(int openYear, String semester, String versionName, float classRoomPrice) {
          OpenCourseTimeVersion byName = openCourseTimeVersionDao.getByName(openYear, semester, versionName);
          if (byName != null && byName.getId() != 0) {
               throw new EasyEduException(DeanServiceError.ERR_OPEN_COURSE_TIME_VERSION_EXIST);
          }
          String versionUUID = UUID.randomUUID().toString().trim();
          Integer integer =
                  openCourseTimeVersionDao.addNewVersion(openYear, semester, versionUUID, versionName, classRoomPrice);
          if (integer != 1) {
               throw new EasyEduException(DbError.Err_INSERT_DB_ERROR, "增加开课版本失败");
          }
     }

     public List<OpenCourseTimeVersion> getArrangeVersion(int openYear, String semester) {
          return openCourseTimeVersionDao.getByOpenYearAndSemester(openYear, semester);
     }
     public RespOpenCourseRange getOpenCourseTimeByVersion(String versionId) {

          return null;
     }
     public RespQueryHasArrangeCourse getHasArrangeCourseInfoByVersion(ReqQueryVersionHasArrangeCourse req) {
          int openYear = req.getOpenYear();
          String semester = req.getSemester();
          int reqFloor = req.getFloor();
          int week = req.getWeek();
          String classroom = req.getClassroom();
          String versionId = req.getVersionId();
          OpenCourseTimeVersion openCourseTimeVersion =
                  timeTableVersionManager.getTimeTableVersion(openYear, semester, versionId);
          //获取已排课课程信息
          List<HasArrangeCourse> hasArrangeCourses = openCourseTimeMidDao
                  .getHasArrangeCourse(openYear, semester, req.getBuilding(), reqFloor, week, classroom,
                          openCourseTimeVersion.getOpenCourseTimeMidVersionId());

          if (hasArrangeCourses == null || hasArrangeCourses.isEmpty()) {
               throw new EasyEduException(DeanServiceError.ERR_CLASS_ROOM_EMPTY, "此教室没有任何课程");
          }
          RespQueryHasArrangeCourse resp = new RespQueryHasArrangeCourse();

          resp.setFloor(reqFloor);
          resp.setClassroom(classroom);
          resp.setCourseInfos(hasArrangeCourses);
          return resp;
     }

     /**
      * 应用排课版本
      *
      * @param versionId 版本id
      */
     public void applyArrange(String versionId) {
          //查询版本
          OpenCourseTimeVersion openCourseTimeVersion = openCourseTimeVersionDao.getByVersionId(versionId);
          if (openCourseTimeVersion == null || openCourseTimeVersion.getId() == 0) {
               throw new EasyEduException(DeanServiceError.ERR_OPEN_COURSE_TIME_VERSION_NOT_EXIST);
          }
          //复制课表
          timeTableManager.copyTimeTableFromMid(openCourseTimeVersion);
     }

     /**
      * 删除课表
      *
      * @param openYear 开课年
      * @param semester 开课学期
      */
     @Transactional(rollbackFor = Exception.class)
     public void deleteArrange(String openYear, String semester) {
          Integer integer = openCourseTimeDao.deleteByYearAndSemester(openYear, semester);
          if (integer == 0) {
               throw new EasyEduException(DbError.Err_DELETE_DB_ERROR, "删除课表失败");
          }
     }
}
